
#import "Common.h"

#import "Renderer.h"

#define USE_DEPTH_BUFFER	true

@interface Renderer()

-(bool) createFrameBuffer;
-(void) destroyFrameBuffer;

@end


@interface Renderer(RenderebleObjects)

-(void) drawRenderebleObjects;

@end

@implementation Renderer

@synthesize backingWidth,backingHeight;
@synthesize glContext;

-(void) logExtensions
{
	EAGLContext *curCtx = [EAGLContext currentContext];
	[EAGLContext setCurrentContext:glContext];
	
	//NSLog(@"Supported OpenGL extensions...");
	//NSLog(@"------------------------------");
	
	NSString *extensionString = [NSString stringWithUTF8String:(char *)glGetString(GL_EXTENSIONS)];
    NSArray *extensions = [extensionString componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    for (NSString *oneExtension in extensions)
       // NSLog(oneExtension);
	
	[EAGLContext setCurrentContext:curCtx];
}

-(bool)initWithLayer:(CAEAGLLayer*) layer
{
	[super init];
	
	renderebleObjectsArray = [[NSMutableArray alloc] init];
	
	pushTransformCount = 0;
	
	clearColor = ColorRGBAMake(0, 0, 0, 1);
	
	glLayer = layer;
	glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
	
	int rc = [glContext retainCount];
	
	if(!glContext || ! [EAGLContext setCurrentContext:glContext])
	{
		rc = [glContext retainCount];
		return false;
	}
	else
	{
		rc = [glContext retainCount];
		
//		backingWidth = [glLayer frame].size.width;
//		backingHeight = [glLayer frame].size.height;
		
		return [self createFrameBuffer];
	}
}

-(void)dealloc
{
	[EAGLContext setCurrentContext:glContext];
	
	[renderebleObjectsArray release];
	
	int rc = [glContext retainCount];
	
	[self destroyFrameBuffer];

	rc = [glContext retainCount];
	
	[EAGLContext setCurrentContext:nil];
	
	rc = [glContext retainCount];
	
	[glContext release];
	
	[super dealloc];
}

- (bool)createFrameBuffer 
{
//	if(glLayer.hidden)
//		return;
	[EAGLContext setCurrentContext:glContext];
	
	backingWidth = [glLayer frame].size.width;
	backingHeight = [glLayer frame].size.height;
	
    glGenFramebuffersOES(1, &frameBuffer);
    glGenRenderbuffersOES(1, &renderBuffer);
    
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, frameBuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, renderBuffer);
    [glContext renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:glLayer];
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, renderBuffer);
    
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
    
    if (USE_DEPTH_BUFFER) {
        glGenRenderbuffersOES(1, &depthBuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthBuffer);
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthBuffer);
    }
	
	//int rc = [glContext retainCount];
    
    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
       // NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return false;
    }
    
	[self logExtensions];
	
    return true;
}


- (void)destroyFrameBuffer 
{
    
	if(frameBuffer)
	{
		glDeleteFramebuffersOES(1, &frameBuffer);
		frameBuffer = 0;
	}
	
	if(renderBuffer)
	{
		glDeleteRenderbuffersOES(1, &renderBuffer);
		renderBuffer = 0;
	}
    
    if(depthBuffer)
	{
        glDeleteRenderbuffersOES(1, &depthBuffer);
			depthBuffer = 0;
    }
}

-(void)onFrameResize
{
	EAGLContext *glCtx = [EAGLContext currentContext];
	
	if([EAGLContext currentContext] != glContext)
		[EAGLContext setCurrentContext:glContext];
	
	[self destroyFrameBuffer];
	[self createFrameBuffer];

	[EAGLContext setCurrentContext:glCtx];
	
	//[activeCamera setViewPortX:0 Y:0 W:backingWidth H:backingHeight];
//	[activeCamera setFov:M_PI/4.0f);
//	[activeCamera setAspectRatio:backingWidth/backingHeight];
}

-(void) setClearColor:(ColorRGBA)color
{
	clearColor = color;
}

- (void)drawBegin
{
//	int rc = [glContext retainCount];
	
	//EAGLContext *glCtx = [EAGLContext currentContext];
	
	if([EAGLContext currentContext] != glContext)
		[EAGLContext setCurrentContext:glContext];
    
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, frameBuffer);
	
	glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);

	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

//	glFrontFace(GL_CW);
//	glCullFace(GL_BACK);
//	glEnable(GL_CULL_FACE);
//
//	glDisable(GL_CULL_FACE);
//	glEnable(GL_DEPTH_TEST);
	
	[self enableBlending];
	
	// Update active camera viewport
	
	[self onCameraChange];
}

- (void)drawEnd
{
	if([EAGLContext currentContext] != glContext)
	{
		return ;
		//[EAGLContext setCurrentContext:glContext];
	}
	
	[self drawRenderebleObjects];
	
	assert(pushTransformCount==0);
	
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, renderBuffer);
    [glContext presentRenderbuffer:GL_RENDERBUFFER_OES];
}

-(void) setCamera:(Camera*)newCamera
{
	activeCamera = newCamera;
	[self onCameraChange];
}

-(void)onCameraChange
{
	CGRect viewPort = [activeCamera viewPort];
	glViewport(viewPort.origin.x,viewPort.origin.y, viewPort.size.width,viewPort.size.height);
	
	Frustum frustum = [activeCamera frustum];
	
	Vector3 camPos = activeCamera.pos;
	Vector3 lookAtPoint = 
	{
		activeCamera.pos.x + activeCamera.forwardVector.x, 
		activeCamera.pos.y + activeCamera.forwardVector.y,
		activeCamera.pos.z + activeCamera.forwardVector.z,
	};

	// Setup projection mtx
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	switch(activeCamera.projection)
	{
		case ProjectionOrtho:
			glOrthof(frustum.left,frustum.right, frustum.bottom,frustum.top,frustum.zNear,frustum.zFar);
			break;
			
		case ProjectionPerspective:
			glFrustumf(frustum.left,frustum.right, frustum.bottom,frustum.top,frustum.zNear,frustum.zFar);
			break;
	}
	Vector3 cameraUpVector = activeCamera.globalUpVector;
	gluLookAt(&camPos,&lookAtPoint,&cameraUpVector);	
	
	// Setup model view mtx
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

- (void) drawTriStripBuffer:(void*)vertexBuffer vertCount:(int)vertCount
{
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertexBuffer);
    
    glDrawArrays(GL_TRIANGLE_STRIP, 0, vertCount);
}

-(void) setVertexBuffer:(void*)buffer vertexComponentsCount:(int)vertSize
{
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(vertSize, GL_FLOAT, 0, buffer);
}

-(void) setNormalBuffer:(void*)buffer normalComponentsCount:(int)normalSize
{
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer(GL_FLOAT, 0, buffer);
}

-(void) setColorBuffer:(void*)buffer colorComponentsCount:(int)_colorSize
{
    glEnableClientState(GL_COLOR_ARRAY);
    glColorPointer(_colorSize, GL_UNSIGNED_BYTE, 0, buffer);
}

-(void) setTexCoordBuffer:(void*)buffer texCoordComponentsCount:(int)coordCount
{
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(coordCount, GL_FLOAT, 0, buffer);
}

-(void) drawPrimitive:(int)primitiveType vertexCount:(int)vertCount
{
	glDrawArrays(primitiveType, 0, vertCount);
}

-(void) drawPrimitiveIndexed:(int)primitiveType primitiveCount:(int)primitiveCount indexBuffer:(void*)indexBuffer
{
	glDrawElements(primitiveType, primitiveCount, GL_UNSIGNED_SHORT, indexBuffer);
}
//
//-(void) setMaterial:(Material*) mat
//{	
//	[self setMaterial:mat indexText:0];
//}
//
//- (void) setMaterial:(Material*)mat indexText:(int)index
//{
//	if(!mat)
//		return ;
//	
//	GLfloat	ambient[] = {mat.ambient.r,mat.ambient.g,mat.ambient.b,mat.ambient.a};
//	GLfloat	diffuse[] = {mat.diffuse.r,mat.diffuse.g,mat.diffuse.b,mat.diffuse.a};
//	GLfloat	specalur[] = {mat.specular.r,mat.specular.g,mat.specular.b,mat.specular.a};
//	GLfloat	emission[] = {mat.emission.r,mat.emission.g,mat.emission.b,mat.emission.a};
//	
//	glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
//	glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
//	glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, specalur);
//	glMaterialfv( GL_FRONT_AND_BACK, GL_EMISSION, emission);
//	glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, mat.shininess);
//
//	if([mat getTexture:index])
//	{
//		[self enableTextures];
//		glBindTexture(GL_TEXTURE_2D, [mat getTexture:index].glTextureName);
//	}
//	else
//	{
//		[self disableTextures];
//	}
//}
//
//- (void) pushTransform:(Transform*)transform
//{
//	pushTransformCount += 1;
//	
//	glMatrixMode(GL_MODELVIEW);
//	glPushMatrix();
//	
//	Matrix4 mtx4;
//	[Utils getOGLMtx:&mtx4 fromTransform:transform];
//	
//	glMultMatrixf((GLfloat*)mtx4.data);
//}
//
//- (void) popTransform
//{
//	glMatrixMode(GL_MODELVIEW);
//	glPopMatrix();
//	
//	pushTransformCount -= 1;
//}


-(void) enableTextures
{
	glEnable(GL_TEXTURE_2D);
}

-(void) disableTextures
{
	glDisable(GL_TEXTURE_2D);
}

-(void) enableBlending
{
	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
}
-(void) disableBlending
{
	glDisable(GL_BLEND);
}

-(void) disableNormalBuffer
{
	glDisableClientState(GL_NORMAL_ARRAY);
}

-(void) disableColorBuffer
{
	glDisableClientState(GL_COLOR_ARRAY);
}

-(void) disableTexCoordBuffer
{
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);	
}
//
//-(void) enableLighting
//{
//	glEnable(GL_LIGHTING);
//}
//
//-(void) disableLighting
//{
//	glDisable(GL_LIGHTING);
//}
//
//-(void) setLight:(Light*)light num:(int)num
//{
//	num += GL_LIGHT0;
//	
//	glLightfv(num, GL_AMBIENT, (float*)&light->ambient);
//	glLightfv(num, GL_DIFFUSE, (float*)&light->diffuse);
//	glLightfv(num, GL_SPECULAR, (float*)&light->specular);
//
//	switch(light->type)
//	{
//		// Infinite pos, use only direction 
//		case LIGHT_DIRECTION:
//			{
//				GLfloat	lightPos[] = {-light->dir.x,-light->dir.y,-light->dir.z,0.0f};
//
//				glLightfv(num, GL_POSITION, lightPos);
//				
//				// Remove spot light type
//				glLightf(num, GL_SPOT_CUTOFF, 180.0f);
//			}
//			break;
//
//		// Same as DIRECTION but not infinite
//		case LIGHT_OMNI:
//			{
//				GLfloat	lightPos[] = {light->pos.x,light->pos.y,light->pos.z,1.0f};
//				
//				glLightfv(num, GL_POSITION, lightPos);
//
//				// Remove spot light type
//				glLightf(num, GL_SPOT_CUTOFF, 180.0f);
//			}
//			break;
//			
//		case LIGHT_SPOT:
//			{
//				GLfloat	lightPos[] = {light->pos.x,light->pos.y,light->pos.z,1.0f};
//				GLfloat	lightDir[] = {light->dir.x,light->dir.y,light->dir.z};
//				
//				glLightfv(num, GL_POSITION, lightPos);
//				
//				glLightfv(num, GL_SPOT_DIRECTION, lightDir);
//
//				//glLightf(num, GL_SPOT_EXPONENT, light->spotExponent);
//				
//				// Set SPOT light type, if spotAngle=360 will have OMNI
//				glLightf(num, GL_SPOT_CUTOFF, light->spotAngle/2);
//			}
//			break;
//	}
//
//	glEnable(num);
//}
//
//-(void) disableLight:(int)num
//{
//	glDisable(GL_LIGHT0 + num);
//}

-(void) enableShading
{
	//glEnable(GL_SHADE_MODEL);
	glShadeModel(GL_SMOOTH);
}
-(void) disableShading
{
	glDisable(GL_SHADE_MODEL);
}

-(void) setGlobalAmbientR:(float)r G:(float)g B:(float)b A:(float)a
{
	float ambient[] = {r,g,b,a};
	
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
}

-(void) enableFaceCulling
{
	glEnable(GL_CULL_FACE);
}

-(void) disableFaceCulling
{
	glDisable(GL_CULL_FACE);
}

- (void) setTexture:(Texture*)texture
{
	glBindTexture(GL_TEXTURE_2D, texture.glTextureName);
}

-(void) drawRendereble:(Rendereble*) renderebleObject
{
	if(!renderebleObject)
		return;
	
	assert(renderebleObject);
	
	Rendereble *rendObj = nil;
	
	float objZ = [renderebleObject getZValue];
	
	int arraySize = [renderebleObjectsArray count];
	
	int i=0;
	for(i=0;i<arraySize;i++)
	{
		rendObj = [renderebleObjectsArray objectAtIndex:i];
		
		if(objZ > [rendObj getZValue])
		{
			[renderebleObjectsArray insertObject:renderebleObject atIndex:i];
			break;
		}
	}
	
	// Object was not inserted in the list, so just add it to end
	if(i==arraySize)
	{
		[renderebleObjectsArray addObject:renderebleObject];
	}
}

-(void) drawRenderebleObjects
{
	int objectsCount = [renderebleObjectsArray count];
	
#define MAX_STATIC_OBJECTS_VERTEX	(10*1024)
	
	static Vector3		drawebleObjVert[MAX_STATIC_OBJECTS_VERTEX];
	static ColorRGBA	drawebleObjColor[MAX_STATIC_OBJECTS_VERTEX];
	static float		drawebleObjTextCoords[2*MAX_STATIC_OBJECTS_VERTEX];
	
	int totalVertexCollected = 0;
	
	Rendereble	*rendObj;
	
//	for(int i=0;i<objectsCount;i++)
//	{
//		rendObj = [renderebleObjectsArray objectAtIndex:i];
//		
//		totalVertexCollected += 
//		[rendObj 
//		 renderSelfToBufferVertex: &drawebleObjVert[totalVertexCollected] 
//		 Color:&drawebleObjColor[totalVertexCollected] 
//		 TextCoord:&drawebleObjTextCoords[totalVertexCollected]
//		];
//	}
	
	// All data already in buffers, need just draw it
	for(int i=0;i<objectsCount;i++)
	{
		rendObj = [renderebleObjectsArray objectAtIndex:i];
		
		totalVertexCollected = 
		[rendObj 
		 renderSelfToBufferVertex: drawebleObjVert 
		 Color:drawebleObjColor 
		 TextCoord:drawebleObjTextCoords
		 ];
		
		if(totalVertexCollected>0)
		{
			[self setTexture:[rendObj getTexture]];
			
			[self setTexCoordBuffer:drawebleObjTextCoords texCoordComponentsCount:2];
			//[self setColorBuffer:drawebleObjColor colorComponentsCount:4];
			[self setVertexBuffer:drawebleObjVert vertexComponentsCount:3];
			
			[self drawPrimitive:GL_TRIANGLES vertexCount:totalVertexCollected];
		}
	}
	
	[renderebleObjectsArray removeAllObjects];
}

@end
